﻿using Ndns.Packet.ResourceRecords;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;

namespace Ndns.Packet
{
    public abstract class DnsResourceRecord : IEquatable<DnsResourceRecord>
    {
        /// <summary>
        /// 表示DNS应答的名称。
        /// </summary>
        public DnsHostName HostName { get; set; }

        /// <summary>
        /// 表示DNS的应答类型。
        /// </summary>
        public abstract DnsRecordType Type { get; set; }

        /// <summary>
        /// 表示DNS的类型。
        /// </summary>
        public DnsClass Class { get; set; }

        /// <summary>
        /// 表示DNS应答的生存时间。
        /// </summary>
        public uint TTL { get; set; }

        /// <summary>
        /// 获取DNS应答数据
        /// </summary>
        /// <returns></returns>
        public abstract byte[] GetData();

        #region 构造DnsResourceRecord实例

        protected DnsResourceRecord()
        {
            HostName = new DnsHostName();
            Class = DnsClass.Internet;
            TTL = 0;
        }

        protected DnsResourceRecord(DnsHostName hostName, DnsClass c, uint ttl)
        {
            HostName = hostName ?? throw new ArgumentNullException(nameof(hostName));
            Class = c;
            TTL = ttl;
        }

        #endregion

        #region 构造DnsResourceRecord实例（支持指针）


        /// <summary>
        /// 从包含数据包的字节数组中以支持指针的方式构造DnsResourceRecord实例。
        /// </summary>
        /// <param name="packet">包含数据包的字节数组。</param>
        /// <param name="packetStartIndex">数据包在字节数组中的起始位置。/</param>
        /// <param name="answerOffset">要解析的应答在数据包中的偏移量。</param>
        /// <returns>解析得到的DnsHostName实例。</returns>
        public static DnsResourceRecord FromPacket(byte[] packet, long packetStartIndex, long answerOffset)
        {
            using (var ms = new MemoryStream(packet))
            {
                ms.Seek(packetStartIndex, SeekOrigin.Begin);
                ms.Seek(answerOffset, SeekOrigin.Current);
                return FromPacket(ms, packetStartIndex);
            }
        }

        /// <summary>
        /// 从包含数据包的流中以支持指针的方式构造DnsResourceRecord实例。
        /// </summary>
        /// <param name="packetStream">数据包流，Position应该指向要解析的应答的开始。</param>
        /// <param name="offset">数据包的开始位置在流中的偏移量。</param>
        /// <returns>解析得到的DnsResourceRecord实例。</returns>
        public static DnsResourceRecord FromPacket(Stream packetStream, long packetStartIndex)
        {
            //记录开始位置
            var pos_start = packetStream.Position;
            //获取各项的值
            var hostName = DnsHostName.FromPacket(packetStream, packetStartIndex);
            var vType = packetStream.ReadNetworkUInt16();
            var vClass = packetStream.ReadNetworkUInt16();
            var vTTL = packetStream.ReadNetworkUInt32();
            int len = packetStream.ReadNetworkUInt16();
            PacketEndOfStreamException.CheckValue(len);
            var vData = new byte[len];
            int bytesRead = packetStream.Read(vData);
            if (bytesRead != len)
                throw new PacketEndOfStreamException();
            //记录结束位置
            var pos_end = packetStream.Position;
            packetStream.Seek(pos_start, SeekOrigin.Begin);
            //按照各种类型解析
            DnsResourceRecord result;
            switch ((DnsRecordType)vType)
            {
                case DnsRecordType.A:
                    return DnsResourceRecordA.FromPacket(packetStream, packetStartIndex);
                case DnsRecordType.NS:
                    return DnsResourceRecordNS.FromPacket(packetStream, packetStartIndex);
                case DnsRecordType.CNAME:
                    return DnsResourceRecordCNAME.FromPacket(packetStream, packetStartIndex);
                case DnsRecordType.SOA:
                    return DnsResourceRecordSOA.FromPacket(packetStream, packetStartIndex);
                case DnsRecordType.PTR:
                    return DnsResourceRecordPTR.FromPacket(packetStream, packetStartIndex);
                case DnsRecordType.MX:
                    return DnsResourceRecordMX.FromPacket(packetStream, packetStartIndex);
                case DnsRecordType.TXT:
                    return DnsResourceRecordTXT.FromPacket(packetStream, packetStartIndex);
                case DnsRecordType.AAAA:
                    return DnsResourceRecordAAAA.FromPacket(packetStream, packetStartIndex);
                default:
                    result = new CustomDnsResourceRecord(hostName, (DnsClass)vClass, (DnsRecordType)vType, (uint)vTTL, vData);
                    break;
            }
            //恢复到流的末尾
            packetStream.Seek(pos_end, SeekOrigin.Begin);
            //返回
            return result;
        }
        
        #endregion

        #region 获取相关信息

        /// <summary>
        /// 将此实例表示的内容写入到流中。
        /// </summary>
        public int CopyTo(Stream s)
        {
            int count;
            count = HostName.CopyTo(s);
            s.WriteNetworkUInt16((ushort)Type);
            s.WriteNetworkUInt16((ushort)Class);
            s.WriteNetworkUInt32(TTL);

            long pos = s.Position;
            s.Seek(2, SeekOrigin.Current);
            int len = CopyDataTo(s);
            long pos2 = s.Position;
            s.Seek(pos, SeekOrigin.Begin);
            s.WriteNetworkUInt16((ushort)len);
            s.Seek(pos2, SeekOrigin.Begin);

            count += 10 + len;
            return count;
        }

        protected virtual int CopyDataTo(Stream s)
        {
            var data = GetData();
            s.Write(data);
            return data.Length;
        }

        public int CopyCompressedTo(Stream s, Dictionary<string, DnsHostNamePointer> symbols, ref int offset, byte[] buffer)
        {
            int count;
            count = HostName.CopyCompressedTo(s, symbols, ref offset);
            s.WriteNetworkUInt16((ushort)Type);
            s.WriteNetworkUInt16((ushort)Class);
            s.WriteNetworkUInt32(TTL);
            count += 8;
            offset += 2 * sizeof(ushort) + 1 * sizeof(uint);

            long pos = s.Position;
            s.Seek(2, SeekOrigin.Current);
            offset += 2;
            int len = CopyCompressedDataTo(s, symbols, ref offset);
            long pos2 = s.Position;

            s.Seek(pos, SeekOrigin.Begin);
            s.WriteNetworkUInt16((ushort)len);
            s.Seek(pos2, SeekOrigin.Begin);

            count += 2 + len;
            return count;
        }

        protected virtual int CopyCompressedDataTo(Stream s, Dictionary<string, DnsHostNamePointer> symbols, ref int offset)
        {
            var data = GetData();
            s.Write(data);
            offset += data.Length;
            return data.Length;
        }

        /// <summary>
        /// 获取字节数组表示的DnsResourceRecord实例。
        /// </summary>
        public byte[] GetBytes()
        {
            using (var ms = new MemoryStream())
            {
                this.CopyTo(ms);
                return ms.ToArray();
            }
        }
        #endregion

        #region Clone,Equals,HashCode实现
        public abstract DnsResourceRecord Clone();

        public override bool Equals(Object obj)
        {
            if (this == null)                        //this is necessary to guard against reverse-pinvokes and
                throw new NullReferenceException();  //other callers who do not use the callvirt instruction

            if (!(obj is DnsResourceRecord))
                return false;

            return EqualsHelper(this, (DnsResourceRecord)obj);
        }

        public bool Equals(DnsResourceRecord other)
        {
            if (this == null)                        //this is necessary to guard against reverse-pinvokes and
                throw new NullReferenceException();  //other callers who do not use the callvirt instruction

            return EqualsHelper(this, other);
        }

        public static bool operator ==(DnsResourceRecord a, DnsResourceRecord b)
        {
            return Equals(a, b);
        }

        public static bool operator !=(DnsResourceRecord a, DnsResourceRecord b)
        {
            return !Equals(a, b);
        }

        public static bool Equals(DnsResourceRecord a, DnsResourceRecord b)
        {
            if (Object.ReferenceEquals(a, b))
            {
                return true;
            }

            return EqualsHelper(a, b);
        }

        protected static bool EqualsHelper(DnsResourceRecord a, DnsResourceRecord b)
        {
            if ((object)a == null && (object)b == null)
                return true;

            if ((object)a == null || (object)b == null)
                return false;

            if (a.Type != b.Type)
                return false;

            if (a.Class != b.Class)
                return false;

            if (a.TTL != b.TTL)
                return false;

            if (a.HostName != b.HostName)
                return false;

            if (b is CustomDnsResourceRecord)
                return b.TestDataEqualsTo(a);
            else
                return a.TestDataEqualsTo(b);
        }

        protected virtual bool TestDataEqualsTo(DnsResourceRecord other)
        {
            return (this.GetData() as IStructuralEquatable).Equals(other.GetData(), StructuralComparisons.StructuralEqualityComparer);
        }

        public override int GetHashCode()
        {
            int total = 17;
            total = total * 37 + HostName.GetHashCode();
            total = total * 37 + Type.GetHashCode();
            total = total * 37 + Class.GetHashCode();
            total = total * 37 + TTL.GetHashCode();
            total = total * 37 + GetDataHashCode();
            return total;
        }

        protected virtual int GetDataHashCode()
        {
            return GetData().GetHashCode();
        }
        #endregion
    }
}
